﻿using System.Collections.Generic;

namespace Behaving
{
    public class BehaviourTree
    {
        private bool _prepared;
        private List<Behaviour> _previouslyRunningBehaviours = new List<Behaviour>(10);
        private List<Behaviour> _currentlyRunningBehaviours = new List<Behaviour>(10);

        public BehaviourTree(Behaviour root = null)
        {
            Root = root;
        }

        public Behaviour Root { get; set; }
        //public int BehaviourCount { get; set; }
        public IEnumerable<Behaviour> CurrentlyRunningBehaviours { get { return _currentlyRunningBehaviours; } }

        public RunningState Tick()
        {
            if (!_prepared)
            {
                Prepare();
            }

            _currentlyRunningBehaviours.Clear();
            RunningState runningState = Root.Tick(this);

            if (runningState != RunningState.Running)
            {
                // No behaviours are running, so stop them all!
                StopAllRunningBehaviours();
            }
            else
            {
                // Remove any old running behaviours that were left from the previous iteration
                StopPreviouslyRunningBehavioursThatHaveStopped();
            }
            SwapBehaviourLists();
            return runningState;
        }

        public void SetIsCurrentlyRunning(Behaviour behaviour)
        {
            behaviour.IsCurrentlyRunning = true;
            _currentlyRunningBehaviours.Add(behaviour);
        }

        public void SetWasRunningPreviousTick(Behaviour behaviour)
        {
            behaviour.IsCurrentlyRunning = true;
            _previouslyRunningBehaviours.Add(behaviour);
        }

        private void StopPreviouslyRunningBehavioursThatHaveStopped()
        {
            // We use the array here to allow for deep lists of allowed behaviours without going
            // into O(n^2). It's slightly slower for small lists, but faster for larger ones.
            //bool[] running = new bool[BehaviourCount];
            //for (int index = 0; index < CurrentlyRunningBehaviours.Count; index++)
            //{
            //    Behaviour behaviour = CurrentlyRunningBehaviours[index];
            //    running[behaviour.BehaviourId] = true;
            //}

            //for (int index = 0; index < _previouslyRunningBehaviours.Count; index++)
            //{
            //    Behaviour previouslyRunningBehaviour = _previouslyRunningBehaviours[index];

            //    if (!running[previouslyRunningBehaviour.BehaviourId])
            //    {
            //        previouslyRunningBehaviour.Stop(this);
            //    }
            //}

            // Old slower method
            for (int index = 0; index < _previouslyRunningBehaviours.Count; index++)
            {
                Behaviour previouslyRunningBehaviour = _previouslyRunningBehaviours[index];
                if (!_currentlyRunningBehaviours.Contains(previouslyRunningBehaviour))
                {
                    previouslyRunningBehaviour.Stop(this);
                }
            }
        }

        private void StopAllRunningBehaviours()
        {
            for (int index = 0; index < _previouslyRunningBehaviours.Count; index++)
            {
                Behaviour previouslyRunningBehaviour = _previouslyRunningBehaviours[index];
                previouslyRunningBehaviour.Stop(this);
            }
        }

        private void SwapBehaviourLists()
        {
            List<Behaviour> temp = _currentlyRunningBehaviours;
            _currentlyRunningBehaviours = _previouslyRunningBehaviours;
            _previouslyRunningBehaviours = temp;
        }

        private void Prepare()
        {
            //BehaviourCount = 0;
            Root.WalkTree(DoPrepare);
            _prepared = true;
        }

        private void DoPrepare(Behaviour behaviour)
        {
            //behaviour.BehaviourId = (byte)BehaviourCount++;
            behaviour.Prepare(this);
        }      
    }
}
